/*
根据CTCSlave.h的定义,此文件为CTC协议从控端的演示文件.用于游戏机IO板程序
使用时需要将CTCBuffer.h,CTCClient.h,CTCSlave.h,CTCMsgV1.h,Debug.h文件添加到工程中,并将CTCSlave.c,CTCBuffer.c文件添加到工程中编译
调试输出信息可参考Debug.h文件定义顶部的说明
*/

#include "CTCSlave.h"
#include "stm32f1xx_hal.h"
#include <stm32f1xx_hal_uart.h>


static uint8_t masterrx_data;     // 用于中断接收单个字节
UART_HandleTypeDef huart1;

// 系统时钟配置
void SystemClock_Config(void);

// 串口配置
void SlaveApp_USART_Config(uint32_t baudrate)
{
    /* 串口基本参数配置 */
    huart1.Instance          = USART1;
    huart1.Init.BaudRate     = baudrate;             // 波特率
    huart1.Init.WordLength   = UART_WORDLENGTH_8B;   // 8位数据位
    huart1.Init.StopBits     = UART_STOPBITS_1;      // 1位停止位
    huart1.Init.Parity       = UART_PARITY_NONE;     // 无奇偶校验
    huart1.Init.Mode         = UART_MODE_TX_RX;      // 收发模式
    huart1.Init.HwFlowCtl    = UART_HWCONTROL_NONE;  // 无硬件流控
    huart1.Init.OverSampling = UART_OVERSAMPLING_16; // 16倍过采样

    /* 初始化串口 */
    HAL_UART_Init(&huart1);
}

void Error_Handler(void)
{
    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1) {
    }
    /* USER CODE END Error_Handler_Debug */
}

/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    /** Initializes the RCC Oscillators according to the specified parameters
     * in the RCC_OscInitTypeDef structure.
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState       = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
    }

    /** Initializes the CPU, AHB and APB buses clocks
     */
    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
        Error_Handler();
    }
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM1) {
        HAL_IncTick();
    }
}


/* UART MSP初始化函数,配置串口IO脚 */
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
    __HAL_RCC_USART1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /* USART1引脚配置: PA9(TX), PA10(RX) */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART1中断配置 */
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  }
}



// 打印数据
void Debug_PrintData(const char *prefix, uint8_t *data, uint16_t len)
{
    #if DEBUG_LEVEL > 0
    SEGGER_RTT_printf(0, "%s:", prefix);
    for (uint16_t i = 0; i < len; i++) {
        SEGGER_RTT_printf(0, "%02X ", data[i]);
    }
    SEGGER_RTT_printf(0, "\r\n");
    #endif
}



/// @brief 串口发送数据回调
/// @param data 指向要发送的数据指针
/// @param len 数据长度
void SlaveApp_OnSendData(uint8_t *data, uint16_t len)
{
    // 这里实现通过串口发送数据的代码
    //使用物理串口发送数据,超时1000ms
    Debug_PrintData("UART Send", data, len);
    HAL_UART_Transmit(&huart1, data, len, 1000);
}

/// @brief 串口接收数据中断,需覆盖系统串口消息接收中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    // 这里实现通过串口接收数据的代码
    if (huart->Instance == USART1) {
        CTC_Slave_ReceiveData(&masterrx_data, 1); // 将接收到的字节写入接收缓冲区
        // 重新启动中断接收
        HAL_UART_Receive_IT(&huart1, &masterrx_data, 1);
    }
}

/// @brief 数据发送结果的回调
/// @param result 发送结果,参考CTC_AckResult
/// @param cmdid 命令ID,参考CTC_CMD_XXX
/// @param transactionId 事务ID,与发送时的事务ID相同
/// @return 返回后续处理动作,参考CTC_ClientSendAction
CTC_MsgReSendAction SlaveApp_OnSlaveSendDataResult(CTC_AckResult result, uint16_t cmdid, uint32_t transactionId)
{
    // 这里接收的只会有几种result结果
    // CTC_ACK_SUCCESS 发送成功,一定删除发送缓冲不会再重发
    // CTC_ACK_UNSUPPORTED 不支持的指令,需要检查协议版本,一定删除发送缓冲不会再重发
    // CTC_ACK_LENGTH_ERR 长度错误,需要检查协议版本和数据是否符合规范,一定删除发送缓冲不会再重发
    // CTC_ACK_TIMEOUT 发送超时,根据返回的结果判断是丢弃还是重发
    if (result == CTC_ACK_SUCCESS) {
        // 消息发送成功
        DEBUG_INFO("CTCTEST Slave Send Data Success,TransactionID: %d\r\n", transactionId);
    } else if (result == CTC_ACK_TIMEOUT) {
        // 消息发送超时了,需要自己判断是否重发
        // 如果是关键数据,可以选择重发 返回CTC_TIMEOUT_DATAACT_RESEND
        // 如果是非关键数据,可以选择删除 返回CTC_TIMEOUT_DATAACT_MISS
        DEBUG_WARN("CTCTEST Slave Send Data Timeout,TransactionID: %d\r\n", transactionId);
        return CTC_TIMEOUT_DATAACT_RESEND; // 重发此消息
    }
    return CTC_TIMEOUT_DATAACT_MISS; // 默认删除此消息
}

/// @brief 连接状态变化,设备状态变化回调
/// @param state 当前连接状态,参考CTC_ClientConnState
/// @param prevState 上一个连接状态,参考CTC_ClientConnState
void SlaveApp_OnSlaveConnStateChange(CTC_ClientConnState state, CTC_ClientConnState prevState)
{
    // 这里处理连接状态变化的代码
    if (state == CTC_CLIENT_CONNSTATE_CONNECTED && prevState != CTC_CLIENT_CONNSTATE_CONNECTED) {
        // 连接成功
        DEBUG_INFO("CTCTEST Slave Connected to Master\r\n");
    } else if (state == CTC_CLIENT_CONNSTATE_CONNECTTING && prevState != CTC_CLIENT_CONNSTATE_CONNECTTING) {
        // 丢失了连接,正在尝试重连
        DEBUG_INFO("CTCTEST Slave Connecting to Master...\r\n");
    } else if (state == CTC_CLIENT_CONNSTATE_OFFLINE) {
        // CTC1.0不会出现此状态,如果出现说明参数不足无法建立连接
        DEBUG_ERROR("CTCTEST Slave Offline,Check Parameters\r\n");
    }
    // 此时可以获取主设备状态
    uint8_t masterstate = CTC_Slave_GetMasterDeviceState();

    if (masterstate == CTC_STATE_UNKNOWN) {
        // 从未连成功过
        DEBUG_WARN("CTCTEST Master device state is unknown.\r\n");
    } else {
        if (masterstate == 0) {
            // 状态为0,表示卡头正常运行,可以上报数据
        } else {
            // 状态非0,表示设备不可用,不能上传数据0=正常可用,1=等待配网,2=连接门店服务异常,3=网络无法连接,4=正在联网
            // 可在游戏屏幕显示状态信息
        }
        DEBUG_INFO("CTCTEST Master device state: %d\r\n", masterstate);
    }
}

// Slave收到抄表命令回调
void SlaveApp_OnSlaveReadMeter(void)
{
    // 卡头在调用抄表命令后,会收到此回调
    // 这里处理抄表命令,一般读取实际的抄表数据并调用CTC_Slave_SendMeterDataRsp发送结果
    DEBUG_INFO("CTCTEST Slave Read Meter Command:\r\n");
    CTC_MeterDataRspMsgV1 rsp;
    rsp.Items[0].MeterType  = CTC_METER_TYPE_COIN_IN_TOTAL;          // 总投币数
    rsp.Items[0].MeterValue = 10;          // 数量
    rsp.Items[1].MeterType  = CTC_METER_TYPE_TICKET_TOTAL;           // 总出彩票数
    rsp.Items[1].MeterValue = 100;        // 数量
    rsp.Items[2].MeterType  = CTC_METER_TYPE_BLUE_TICKET_TOTAL;      // 总出蓝票数
    rsp.Items[2].MeterValue = 200;          // 数量
    rsp.Items[3].MeterType  = CTC_METER_TYPE_PHYSICAL_REWARD1_TOTAL; // 总出礼品数
    rsp.Items[3].MeterValue = 10;          // 数量
    rsp.Items[4].MeterType  = CTC_METER_TYPE_END;                    // 结束
    CTC_Slave_SendMeterDataRsp(&rsp);
}

// Slave收到Master投币命令回调
void SlaveApp_OnSlaveCoinIn(const CTC_CoinInMsgV1 *msg)
{
    // 卡头在调用投币命令后,会收到此回调
    // 这里一般将投币累计到游戏机投币数中,并且回应投币结果
    // 这里假设投币成功
    DEBUG_INFO("CTCTEST Slave Coin In Command:\r\n");
    DEBUG_INFO("  CoinCount: %d\r\n", msg->CoinCount);
    CTC_CoinInRspMsgV1 rsp;
    rsp.Result = 0;                          // 0=成功,1=失败
    memcpy(rsp.RecordId, msg->RecordId, 16); // 回传消费记录ID
    CTC_Slave_SendCoinInRsp(&rsp);
}

// Slave收到通道切换命令回调
void SlaveApp_OnSlaveChannelSel(const CTC_ChannelSelMsgV1 *msg)
{
    // 卡头在调用通道切换命令后,会收到此回调
    // 这里处理通道切换命令,一般切换游戏机的币种通道
    // 这里假设切换成功
    DEBUG_INFO("CTCTEST Slave Channel Select Command:\r\n");
    DEBUG_INFO("  Ticket: %d, BlueTicket: %d\r\n", msg->Ticket, msg->BlueTicket);
}

// Slave收到单价设置命令回调
void SlaveApp_OnSlaveSetCoinPrice(const CTC_CoinPriceMsgV1 *msg)
{
    // 卡头在调用单价设置命令后,会收到此回调
    // 这里处理单价设置命令,一般设置游戏机的单价
    // 这里假设设置成功
    DEBUG_INFO("CTCTEST Slave Set Coin Price Command:\r\n");
    DEBUG_INFO("  CoinPerGame: %d\r\n", msg->CoinPerGame);
}

// Slave收到单价查询命令回调
void SlaveApp_OnSlaveQueryCoinPrice(void)
{
    // 卡头在调用单价查询命令后,会收到此回调
    // 这里处理单价查询命令,一般读取游戏机的单价并调用CTC_Slave_SendQueryCoinPriceRsp发送结果
    DEBUG_INFO("CTCTEST Slave Query Coin Price Command:\r\n");
    CTC_CoinPriceMsgV1 rsp;
    rsp.CoinPerGame = 4; // 每局币数
    CTC_Slave_SendQueryCoinPriceRsp(&rsp);
}

//系统业务其他代码
void SlaveApp_OtherTask(void)
{
    // 这里处理其他业务代码
    
    //实体奖励上报
    //如果对方已收到会通过SlaveApp_OnSlaveSendDataResult回调通知发送成功
    //如果超时会自动重发3次,如果仍然超时会通过SlaveApp_OnSlaveSendDataResult回调通知发送失败,询问是否重发
    CTC_PhysicalReportMsgV1 phyreward;
    phyreward.PhysicalType  = CTC_PHYSICAL_REWARD_TYPE_REALCOIN; // 实物投币
    phyreward.PhysicalCount = 1000;  // 实物数量
    CTC_Slave_SendPhysicalReward(&phyreward);

    //电子奖励
    //如果对方已收到会通过SlaveApp_OnSlaveSendDataResult回调通知发送成功
    //如果超时会自动重发3次,如果仍然超时会通过SlaveApp_OnSlaveSendDataResult回调通知发送失败,询问是否重发
    CTC_RewardMsgV1 reward;
    reward.RewardType  = CTC_REWARD_TYPE_TICKET; // 彩票奖励
    reward.RewardCount = 100; // 奖励1~10张彩票
    CTC_Slave_SendGameReward(&reward);

    //氛围事件
    //如果对方已收到会通过SlaveApp_OnSlaveSendDataResult回调通知发送成功
    //如果超时会自动重发3次,如果仍然超时会通过SlaveApp_OnSlaveSendDataResult回调通知发送失败,询问是否重发
    CTC_AtmosphereDataV1 ambiance;
    ambiance.EventLevel = CTC_ATMOSPHERE_LEVEL_MEDIUM; // 事件等级
    ambiance.RewardType  = CTC_ATMOSPHERE_REWARD_TICKET; // 彩票奖励
    ambiance.RewardCount = 300; // 奖励1000张彩票
    CTC_Slave_SendAtmosphereEvent(&ambiance); 

    //设置设备状态
    //设置后会通过心跳实时传递给主设备
    //注意第二个参数可消费设置成1时,主设备会禁止投币,直到设置成0才允许投币
    CTC_Slave_SetSlaveState(0,0);

    //获取主设备状态
    //状态0=正常可上报数据
    //1=等待配网,2=连接门店服务异常,3=网络无法连接,4=正在联网,可在游戏屏幕显示
    uint8_t masterstate = CTC_Slave_GetMasterDeviceState();
}

// 示例主函数
int main(void)
{
    // 初始化硬件
    HAL_Init(); // 初始化HAL库
    SystemClock_Config(); // 配置系统时钟
    SlaveApp_USART_Config(38400); // 初始化串口,CTC协议波特率38400

    #if DEBUG_LEVEL > 0
    // 初始化SEGGER RTT用于调试输出    
    SEGGER_RTT_ConfigUpBuffer(0, "RTTUP", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);     /* 配置通道 0，上行配置*/
    SEGGER_RTT_ConfigDownBuffer(0, "RTTDOWN", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP); /* 配置通道 0，下行配置*/
    SEGGER_RTT_SetTerminal(0); // 设置打印通道0
    #endif

    // 初始化CTC从控端
    CTC_Slave_Init();
    //设置回调函数
    CTC_Slave_SetSendDataCallback(SlaveApp_OnSendData);                 // 设置从设备数据发送函数
    CTC_Slave_SetDataSendResultCallback(SlaveApp_OnSlaveSendDataResult);     // 设置从设备数据发送结果回调
    CTC_Slave_SetMasterStateChangeCallback(SlaveApp_OnSlaveConnStateChange); // 设置从设备连接状态变化回调
    CTC_Slave_SetReadMeterCallback(SlaveApp_OnSlaveReadMeter);               // 设置从设备抄表命令回调
    CTC_Slave_SetCoinInCallback(SlaveApp_OnSlaveCoinIn);                     // 设置从设备投币命令回调
    CTC_Slave_SetChannelSelCallback(SlaveApp_OnSlaveChannelSel);             // 设置从设备通道切换命令回调
    CTC_Slave_SetSetCoinPriceCallback(SlaveApp_OnSlaveSetCoinPrice);         // 设置从设备单价设置命令回调
    CTC_Slave_SetQueryCoinPriceCallback(SlaveApp_OnSlaveQueryCoinPrice);     // 设置从设备单价查询命令回调

    // 启动串口中断接收,每个字节都触发中断
    HAL_UART_Receive_IT(&huart1, &masterrx_data, 1);

    // 主循环
    while (1)
    {
        // 每1ms调用一次主循环处理函数
        CTC_Slave_Loop1ms();
        // 其他任务处理
        HAL_Delay(1); // 延时1ms
        // if(HAL_GetTick()%1000 == 0){
        //     DEBUG_INFO("CTC Slave Buffer Data length: %d\r\n", CTC_Slave_GetReceiveBufferDataLength());
        // }
    }
}

